use crate::blockchain::Block;
use crate::util::bytes_to_hex_string;
use num_bigint::BigUint;
use num_traits::identities::One;
use sha2::{Digest, Sha256};
use std::cell::RefCell;
use std::cmp::Ordering;

const TARGET_BITS: u32 = 16;
const MAX_NONCE: i64 = i64::MAX;

pub struct ProofOfWork {
    block: RefCell<Block>,
    target: BigUint,
}

impl ProofOfWork {

    pub fn new_proof_of_work(block: RefCell<Block>) -> ProofOfWork {
        let mut target = BigUint::one();
        target = target << (256 - TARGET_BITS);
        ProofOfWork {
            block,
            target,
        }
    }

    pub fn prepare_data(self: &ProofOfWork, nonce: i64) -> Vec<u8> {
        let mut data = vec![];
        data = [data, self.block.borrow().prev_block_hash.to_owned()].concat();
        data = [data, self.block.borrow().hash_transactions()].concat();
        data = [data, Vec::from(self.block.borrow().timestamp.to_be_bytes())].concat();
        data = [data, Vec::from(TARGET_BITS.to_be_bytes())].concat();
        data = [data, Vec::from(nonce.to_be_bytes())].concat();
        data
    }

    pub fn run(self: &ProofOfWork) -> (i64, Vec<u8>) {
        let mut hash = vec![];
        let mut nonce = 0;
        // let output_data = &self.block.borrow().data;
        // println!("Mining the block containing \"{:?}\"", String::from_utf8(output_data.to_vec()).unwrap());

        while nonce < MAX_NONCE {
            let data = self.prepare_data(nonce);

            let mut hasher = Sha256::new();
            hasher.update(&data);
            hash = hasher.finalize().to_vec();
            print!("\r{}", bytes_to_hex_string(&hash));
            let hash_int = BigUint::from_bytes_be(hash.as_slice());

            if hash_int.cmp(&self.target) == Ordering::Less {
                break;
            } else {
                nonce += 1;
            }
        }

        println!();
        println!();

        (nonce, hash)
    }


    pub fn validate(&self) -> bool {
        let data = self.prepare_data(self.block.borrow().nonce);
        let mut hasher = Sha256::new();
        hasher.update(data);
        let hash = hasher.finalize().to_vec();

        BigUint::from_bytes_be(hash.as_slice()).cmp(&self.target) == Ordering::Less
    }
}